
/*
 *  Hamlib AOR backend - AR7030 Plus utility functions
 *  Copyright (c) 2009-2010 by Larry Gadallah (VE6VQ)
 *
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public
 *   License as published by the Free Software Foundation; either
 *   version 2.1 of the License, or (at your option) any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

/*
 * Version 2009.12.31 Larry Gadallah (VE6VQ)
 */

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <assert.h>

#include <hamlib/rig.h>
#include "ar7030p.h"
#include "serial.h"
#include "idx_builtin.h"

static enum PAGE_e curPage = NONE; /* Current memory page */
static unsigned int curAddr = 65535; /* Current page address */
static enum LOCK_LVL_e curLock = LOCK_0; /* Current lock level */
static const unsigned int PAGE_SIZE[] = {
  256, 256, 512, 4096, 4096,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  8
}; /* Page size table */


#if 0
/*
 *     Code Ident Operation
 *     0x   NOP   No Operation
 */
int NOP( RIG *rig, unsigned char x )
{
  int rc = -RIG_OK;

  unsigned char op = ( ( 0x0f & x ) | op_NOP );

  assert( NULL != rig );

  rc = write_block( &rig->state.rigport, ( char * ) &op, 1 );
  if ( 0 != rc )
  {
    rc = -RIG_EIO;
  }

  return ( rc );
}

/*
 *     Code Ident Operation
 *     3x   SRH   Set H-register    x -> H-register (4-bits)
 */
int SRH( RIG *rig, unsigned char x )
{
  int rc = -RIG_OK;

  unsigned char op = ( ( 0x0f & x ) | op_SRH );

  assert( NULL != rig );

  rc = write_block( &rig->state.rigport, ( char * ) &op, 1 );
  if ( 0 != rc )
  {
    rc = -RIG_EIO;
  }

  return ( rc );
}

/*
 *     Code Ident Operation
 *     5x   PGE   Set page          x -> Page register (4-bits)
 */
int PGE( RIG *rig, enum PAGE_e page )
{
  int rc = -RIG_OK;

  unsigned char op = ( ( 0x0f & page ) | op_PGE );

  assert( NULL != rig );

  switch ( page )
  {
  case WORKING:
  case BBRAM:
  case EEPROM1:
  case EEPROM2:
  case EEPROM3:
  case ROM:
    rc = write_block( &rig->state.rigport, ( char * ) &op, 1 );
    if ( 0 != rc )
    {
      rc = -RIG_EIO;
    }
    break;

  case NONE:
  default:
    rig_debug( RIG_DEBUG_VERBOSE, "PGE: invalid page %d\n", page );

    rc = -RIG_EINVAL;
    break;
  };

  return ( rc );
}

/*
 *     Code Ident Operation
 *     4x   ADR   Set address       0Hx -> Address register (12-bits)
 *                                  0 -> H-register
 */
int ADR( RIG *rig, unsigned char x )
{
  int rc = -RIG_OK;

  unsigned char op = ( ( 0x0f & x ) | op_ADR );

  assert( NULL != rig );

  rc = write_block( &rig->state.rigport, ( char * ) &op, 1 );
  if ( 0 != rc )
  {
    rc = -RIG_EIO;
  }

  return ( rc );
}

/*
 *     Code Ident Operation
 *     1x   ADH   Set address high  x -> Address register (high 4-bits)
 */
int ADH( RIG *rig, unsigned char x )
{
  int rc = -RIG_OK;

  unsigned char op = ( ( 0x0f & x ) | op_ADH );

  assert( NULL != rig );

  rc = write_block( &rig->state.rigport, ( char * ) &op, 1 );
  if ( 0 != rc )
  {
    rc = -RIG_EIO;
  }

  return ( rc );
}

/*
 *     Code Ident Operation
 *     6x   WRD   Write data        Hx -> [Page, Address]
 *                                  Address register + 1 -> Address register
 *                                  0 -> H-register, 0 -> Mask register
 */
int WRD( RIG *rig, unsigned char out )
{
  int rc = -RIG_OK;

  unsigned char op = ( ( 0x0f & out ) | op_WRD );

  assert( NULL != rig );

  rc = write_block( &rig->state.rigport, ( char * ) &op, 1 );
  if ( 0 != rc )
  {
    rc = -RIG_EIO;
  }

  return ( rc );
}

/*
 *     Code Ident Operation
 *     9x   MSK   Set mask          Hx -> Mask register <1>
 *                                  0 -> H-register
 */
int MSK( RIG *rig, unsigned char mask )
{
  int rc = -RIG_OK;

  unsigned char op = ( ( 0x0f & mask ) | op_MSK );

  assert( NULL != rig );

  rc = write_block( &rig->state.rigport, ( char * ) &op, 1 );
  if ( 0 != rc )
  {
    rc = -RIG_EIO;
  }

  return ( rc );
}

/*
 *     Code Ident Operation
 *     2x   EXE   Execute routine x
 */
int EXE( RIG *rig, enum ROUTINE_e routine )
{
  int rc = -RIG_OK;

  unsigned char op = ( ( 0x0f & routine ) | op_EXE );

  assert( NULL != rig );

  switch ( routine )
  {
  case RESET:
  case SET_FREQ:
  case SET_MODE:
  case SET_PASS:
  case SET_ALL:
  case SET_AUDIO:
  case SET_RFIF:
  case DIR_RX_CTL:
  case DIR_DDS_CTL:
  case DISP_MENUS:
  case DISP_FREQ:
  case DISP_BUFF:
  case READ_SIGNAL:
  case READ_BTNS:
    rc = write_block( &rig->state.rigport, ( char * ) &op, 1 );
    if ( 0 != rc )
    {
      rc = -RIG_EIO;
    }
    break;

  default:
    rig_debug( RIG_DEBUG_VERBOSE, "EXE: invalid routine %d\n", routine );

    rc = -RIG_EINVAL;
    break;
  };

  return ( rc );
}

/*
 *     Code Ident Operation
 *     7x   RDD   Read data         [Page, Address] -> Serial output
 *                                  Address register + x -> Address register
 */
int RDD( RIG *rig, unsigned char len )
{
  int rc = -RIG_OK;
  unsigned char inChr = 0;
  unsigned char op = ( ( 0x0f & len ) | op_RDD );

  assert( NULL != rig );

  rc = write_block( &rig->state.rigport, ( char * ) &op, 1 );
  if ( 0 != rc )
  {
    rc = -RIG_EIO;
  }
  else
  {
    rc = read_block( &rig->state.rigport, ( char * ) &inChr, len );
    if ( 1 != rc )
    {
      rc = -RIG_EIO;
    }
    else
    {
      rc = (int) inChr;
    }
  }

  return ( rc );
}

/*
 *     Code Ident Operation
 *     8x   LOC   Set lock level x
 */
int LOC( RIG *rig, enum LOCK_LVL_e level )
{
  int rc = -RIG_OK;

  unsigned char op = ( ( 0x0f & level ) | op_LOC );

  assert( NULL != rig );

  switch ( level )
  {
  case LOCK_0:
  case LOCK_1:
  case LOCK_2:
  case LOCK_3:
    rc = write_block( &rig->state.rigport, ( char * ) &op, 1 );
    if ( 0 != rc )
    {
      rc = -RIG_EIO;
    }
    break;

  default:
    rig_debug( RIG_DEBUG_VERBOSE, "LOC: invalid lock level %d\n", level );

    rc = -RIG_EINVAL;
    break;
  };

  return ( rc );
}

/*
 *     Code Ident Operation
 *     Ax   BUT   Operate button x  <1>
 */
int BUT( RIG *rig, enum BUTTON_e button )
{
  int rc = -RIG_OK;

  unsigned char op = ( ( 0x0f & button ) | op_BUT );

  assert( NULL != rig );

  switch ( button )
  {
  case BTN_NONE:
    break;

  case BTN_UP:
  case BTN_DOWN:
  case BTN_FAST:
  case BTN_FILTER:
  case BTN_RFIF:
  case BTN_MEMORY:
  case BTN_STAR:
  case BTN_MENU:
  case BTN_POWER:
    rc = write_block( &rig->state.rigport, ( char * ) &op, 1 );
    if ( 0 != rc )
    {
      rc = -RIG_EIO;
    }
    break;

  default:
    rig_debug( RIG_DEBUG_VERBOSE, "BUT: invalid button %d\n", button );

    rc = -RIG_EINVAL;
    break;
  };

  return ( rc );
}

#endif // 0

/*
 * /brief Execute routine
 *
 * /param rig Pointer to rig struct
 * /param rtn Receiver routine to execute
 *
 * \return RIG_OK on success, error code on failure
 *
 */
int execRoutine( RIG * rig, enum ROUTINE_e rtn )
{
  int rc = -RIG_EIO;
  unsigned char v = EXE( (rtn & 0x0f) );

  assert( NULL != rig );

  if ( 0 == write_block( &rig->state.rigport, (char *) &v, 1 ) )
  {
    rc = -RIG_OK;

    rig_debug( RIG_DEBUG_VERBOSE, "%s: routine %2d\n", __func__, rtn );
  }

  return( rc );
}

/*
 * /brief Set address for I/O with radio
 *
 * /param rig Pointer to rig struct
 * /param page Memory page number (0-4, 15)
 * /param addr Address offset within page (0-4095, depending on page)
 *
 * \return RIG_OK on success, error code on failure
 *
 * Statics curPage and curAddr shadow radio's copies so that
 * page and address are only set when needed
 */
static int setAddr( RIG * rig, enum PAGE_e page, unsigned int addr )
{
  int rc = RIG_OK;
  unsigned char v;

  assert( NULL != rig );

  if ( ( EEPROM3 >= page ) || ( ROM == page ) )
  {
    if ( PAGE_SIZE[page] > addr )
    {
      if ( curPage != page )
      {
        v = PGE( page );
	if ( 0 == write_block( &rig->state.rigport, (char *) &v, 1 ) )
        {
	  curPage = page;
	  rc = -RIG_OK;

	  rig_debug( RIG_DEBUG_VERBOSE, "%s: set page %2d\n", __func__, page );
	}
        else
	{
	  rc = -RIG_EIO;
	}
      }

      if ( curAddr != addr )
      {
        v = SRH( ( 0x0f0 & addr ) >> 4 );
	if ( 0 == write_block( &rig->state.rigport, (char *) &v, 1 ) )
        {
	  rc = -RIG_OK;
	}
        else
	{
	  rc = -RIG_EIO;
	}

        v = ADR( ( 0x00f & addr ) );
	if ( 0 == write_block( &rig->state.rigport, (char *) &v, 1 ) )
        {
	  if ( 0xff < addr )
	  {
	    v = ADH( ( 0xf00 & addr ) >> 8 );
	    if ( 0 == write_block( &rig->state.rigport, (char *) &v, 1 ) )
	    {
	      curAddr = addr;
	      rc = -RIG_OK;

	      rig_debug( RIG_DEBUG_VERBOSE, "%s: set addr 0x%04x\n", __func__, addr );
	    }
	    else
	    {
	      rc = -RIG_EIO;
	    }
	  }
          else
	  {
	    curAddr = addr;
	    rc = -RIG_OK;

	    rig_debug( RIG_DEBUG_VERBOSE, "%s: set addr 0x%04x\n", __func__, addr );
          }
	}
        else
	{
	  rc = -RIG_EIO;
	}
      }
    }
    else
    {
      rc = -RIG_EINVAL; /* invalid address */
    }
  }
  else
  {
    rc = -RIG_EINVAL; /* invalid page */
  }

  return( rc );
}

/*
 * /brief Write one byte to the receiver
 *
 * /param rig Pointer to rig struct
 * /param page Memory page number (0-4, 15)
 * /param addr Address offset within page (0-4095, depending on page)
 * /param x    Value to write to radio
 *
 * \return RIG_OK on success, error code on failure
 *
 */
int writeByte( RIG *rig, enum PAGE_e page, unsigned int addr, unsigned char x )
{
  int rc = -RIG_EIO;
  unsigned char hi = SRH((x & 0xf0 ) >> 4 );
  unsigned char lo = WRD( x & 0x0f );

  assert( NULL != rig );

  rc = setAddr( rig, page, addr );
  if ( RIG_OK == rc )
  {
    rc = -RIG_EIO;
    if ( 0 == write_block( &rig->state.rigport, (char *) &hi, 1 ) )
    {
      if ( 0 == write_block( &rig->state.rigport, (char *) &lo, 1 ) )
      {
	rc = -RIG_OK;
	curAddr++;

	rig_debug( RIG_DEBUG_VERBOSE, "%s: wrote byte 0x%02x\n", __func__, x );
      }
    }
  }

  return( rc );
}

/*
 * /brief Write two bytes to the receiver
 *
 * /param rig  Pointer to rig struct
 * /param page Memory page number (0-4, 15)
 * /param addr Address offset within page (0-4095, depending on page)
 * /param x    Value to write to radio
 *
 * \return Number of bytes written, 0 on error. Get error code with getErrno.
 *
 */
int writeShort( RIG *rig, enum PAGE_e page, unsigned int addr, unsigned short x )
{
  int rc = -RIG_EIO;
  unsigned char v = (unsigned char) ( ( x & 0xff00 ) >> 8 );

  rc = writeByte( rig, page, addr, v );
  if ( RIG_OK == rc )
  {
    v = (unsigned char) ( x & 0x00ff );
    rc = writeByte( rig, page, addr + 1, v );
  }

  return( rc );
}

/*
 * /brief Write three bytes to the receiver
 *
 * /param rig  Pointer to rig struct
 * /param page Memory page number (0-4, 15)
 * /param addr Address offset within page (0-4095, depending on page)
 * /param x    Value to write to radio
 *
 * \return Number of bytes written, 0 on error. Get error code with getErrno.
 *
 */
int write3Bytes( RIG *rig, enum PAGE_e page, unsigned int addr, unsigned int x )
{
  int rc = -RIG_EIO;
  unsigned char v = (unsigned char) ( ( x & 0xff0000 ) >> 16 );

  rc = writeByte( rig, page, addr, v );
  if ( RIG_OK == rc )
  {
    v = (unsigned char) ( ( x & 0x00ff00 ) >> 8 );
    rc = writeByte( rig, page, addr + 1, v );
    if ( RIG_OK == rc )
    {
      v = (unsigned char) ( x & 0x0000ff );
      rc = writeByte( rig, page, addr + 2, v );
    }
  }

  return( rc );
}

/*
 * /brief Write unsigned int (4 bytes) to the receiver
 *
 * /param rig  Pointer to rig struct
 * /param page Memory page number (0-4, 15)
 * /param addr Address offset within page (0-4095, depending on page)
 * /param x    Value to write to radio
 *
 * \return Number of bytes written, 0 on error. Get error code with getErrno.
 *
 */
int writeInt( RIG *rig, enum PAGE_e page, unsigned int addr, unsigned int x )
{
  int rc = -RIG_EIO;
  unsigned char v = (unsigned char) ( ( x & 0xff000000 ) >> 24 );

  rc = writeByte( rig, page, addr, v );
  if ( RIG_OK == rc )
  {
    v = (unsigned char) ( ( x & 0x00ff0000 ) >> 16 );
    rc = writeByte( rig, page, addr + 1, v );
    if ( RIG_OK == rc )
    {
      v = (unsigned char) ( ( x & 0x0000ff00 ) >> 8 );
      rc = writeByte( rig, page, addr + 2, v );
      if ( RIG_OK == rc )
      {
	v = (unsigned char) ( x & 0x000000ff );
	rc = writeByte( rig, page, addr + 3, v );
      }
    }
  }

  return( rc );
}

/*
 * /brief Read one byte from the receiver
 *
 * /param rig Pointer to rig struct
 * /param page Memory page number (0-4, 15)
 * /param addr Address offset within page (0-4095, depending on page)
 * /param x    Pointer to value to read from radio
 *
 * \return RIG_OK on success, error code on failure
 *
 */
int readByte( RIG *rig, enum PAGE_e page, unsigned int addr, unsigned char *x )
{
  int rc = -RIG_OK;
  unsigned char v = RDD( 1 ); // Read command

  assert( NULL != rig );
  assert( NULL != x );

  rc = setAddr( rig, page, addr );
  if ( RIG_OK == rc )
  {
    rc = -RIG_EIO;
    if ( 0 == write_block( &rig->state.rigport, (char *) &v, 1 ) )
    {
      if ( 1 == read_block( &rig->state.rigport, (char *) x, 1 ) )
      {
	curAddr++;
        rc = -RIG_OK;

	rig_debug( RIG_DEBUG_VERBOSE, "%s: read 0x%02x\n", __func__, *x );
      }
    }
  }

  return( rc );
}

/*
 * /brief Read an unsigned short (two bytes) from the receiver
 *
 * /param rig Pointer to rig struct
 * /param page Memory page number (0-4, 15)
 * /param addr Address offset within page (0-4095, depending on page)
 * /param x    Pointer to value to read from radio
 *
 * \return RIG_OK on success, error code on failure
 *
 */
int readShort( RIG *rig, enum PAGE_e page, unsigned int addr, unsigned short *x )
{
  int rc = -RIG_OK;
  unsigned char v;

  assert( NULL != rig );
  assert( NULL != x );

  rc = readByte( rig, page, addr, &v );
  if ( RIG_OK == rc )
  {
    *x = (unsigned short) v << 8;
    rc = readByte( rig, page, addr + 1, &v );
    if ( RIG_OK == rc )
    {
      *x += (unsigned short) v;

      rig_debug( RIG_DEBUG_VERBOSE, "%s: read 0x%04x\n", __func__, *x );
    }
  }

  return( rc );
}

/*
 * /brief Read an unsigned int (three bytes) from the receiver
 *
 * /param rig Pointer to rig struct
 * /param page Memory page number (0-4, 15)
 * /param addr Address offset within page (0-4095, depending on page)
 * /param x    Pointer to value to read from radio
 *
 * \return RIG_OK on success, error code on failure
 *
 */
int read3Bytes( RIG *rig, enum PAGE_e page, unsigned int addr, unsigned int *x )
{
  int rc = -RIG_OK;
  unsigned char v;

  assert( NULL != rig );
  assert( NULL != x );

  rc = readByte( rig, page, addr, &v );
  if ( RIG_OK == rc )
  {
    *x = (unsigned int) v << 16;
    rc = readByte( rig, page, addr + 1, &v );
    if ( RIG_OK == rc )
    {
      *x += (unsigned int) v << 8;
      rc = readByte( rig, page, addr + 2, &v );
      if ( RIG_OK == rc )
      {
	*x += (unsigned int) v;

        rig_debug( RIG_DEBUG_VERBOSE, "%s: read 0x%06x\n", __func__, *x );
      }
    }
  }

  return( rc );
}

/*
 * /brief Read an unsigned int (four bytes) from the receiver
 *
 * /param rig Pointer to rig struct
 * /param page Memory page number (0-4, 15)
 * /param addr Address offset within page (0-4095, depending on page)
 * /param x    Pointer to value to read from radio
 *
 * \return RIG_OK on success, error code on failure
 *
 */
int readInt( RIG *rig, enum PAGE_e page, unsigned int addr, unsigned int *x )
{
  int rc = 0;
  unsigned char v;

  assert( NULL != rig );
  assert( NULL != x );

  rc = readByte( rig, page, addr, &v );
  if ( RIG_OK == rc )
  {
    *x = (unsigned int) v << 24;
    rc = readByte( rig, page, addr + 1, &v );
    if ( RIG_OK == rc )
    {
      *x += (unsigned int) v << 16;
      rc = readByte( rig, page, addr + 2, &v );
      if ( RIG_OK == rc )
      {
	*x += (unsigned int) v << 8;
        rc = readByte( rig, page, addr + 3, &v );
        {
	  *x += (unsigned int) v;

	  rig_debug( RIG_DEBUG_VERBOSE, "%s: read 0x%08x\n", __func__, *x );
        }
      }
    }
  }

  return( rc );
}

/*
 * /brief Read raw AGC value from the radio
 *
 * /param rig Pointer to rig struct
 *
 * \return RIG_OK on success, error code on failure
 */
int readSignal( RIG * rig, unsigned char *x )
{
  int rc = -RIG_EIO;

  assert( NULL != rig );
  assert( NULL != x );

  rc = execRoutine( rig, READ_SIGNAL ); // Read raw AGC value
  if ( RIG_OK == rc )
  {
    if ( 1 == read_block( &rig->state.rigport, (char *) x, 1 ) )
    {
      rc = -RIG_OK;

      rig_debug( RIG_DEBUG_VERBOSE, "%s: raw AGC %03d\n", __func__, *x );
    }
  }

  return( rc );
}

/*
 * /brief Flush I/O with radio
 *
 * /param rig Pointer to rig struct
 *
 */
int flushBuffer( RIG * rig )
{
  int rc = -RIG_EIO;
  char v = '/';

  assert( NULL != rig );

  if ( 0 == write_block( &rig->state.rigport, &v, 1 ) )
  {
    rc = -RIG_OK;
  }

  return( rc );
}

/*
 * /brief Lock receiver for remote operations
 *
 * /param rig Pointer to rig struct
 * /param level Lock level (0-3)
 *
 */
int lockRx( RIG * rig, enum LOCK_LVL_e level )
{
  int rc = -RIG_EIO;
  unsigned char v;

  assert( NULL != rig );

  if ( LOCK_NONE > level ) /* valid level? */
  {
    if ( curLock != level ) /* need to change level? */
    {
      v = LOC( level );

      if ( 0 == write_block( &rig->state.rigport, (char *) &v, 1 ) )
      {
	rc = -RIG_OK;

	curLock = level;
      }
    }
    else
    {
      rc = -RIG_OK;
    }
  }
  else
  {
    rc = -RIG_EINVAL;
  }

  return( rc );
}

/*
 * \brief Convert one byte BCD value to int
 *
 * \param bcd BCD value (0-99)
 *
 * \return Integer value of BCD parameter (0-99), -1 on failure
 */
int bcd2Int( const unsigned char bcd )
{
  int rc = -1;
  unsigned char hi = ((bcd & 0xf0) >> 4);
  unsigned char lo =  (bcd & 0x0f);

  if ( (unsigned char) 0x0a > hi )
  {
    rc = (int) hi * 10;
    if ( (unsigned char) 0x0a > lo )
    {
      rc += (int) lo;
    }
    else
    {
      rc = -1;
    }
  }

  return( rc );
}

/*
 * \brief Convert int into 2 digit BCD number
 *
 * \param int Integer value (0-99)
 *
 * \return 2 digit BCD equvalent (0-99), 0xff on failure
 */
unsigned char int2BCD( const unsigned int val )
{
  unsigned char rc = (unsigned char) 0xff;
  unsigned char tens = (unsigned char) (val / 10);
  unsigned char ones = (unsigned char) (val % 10);

  if ( (unsigned char) 10 > tens )
  {
    rc = ( tens << 4 );
    if ( (unsigned char) 10 > ones )
    {
      rc = rc | ones;
    }
    else
    {
      rc = (unsigned char) 0xff;
    }
  }

  return ( rc );
}

/*
 * \brief Convert raw AGC value to calibrated level in dBm
 *
 * \param rig Pointer to rig struct
 * \param rawAgc raw AGC value (0-255)
 * \param tab Pointer to calibration table struct
 * \param dbm Pointer to value to hold calibrated level (S9 = 0 dBm)
 *
 * \return RIG_OK on success, error code on failure
 *
 * To calculate the signal level, table values should be subtracted from
 * the AGC voltage in turn until a negative value would result. This gives
 * the rough level from the table position. The accuracy can be improved by
 * proportioning the remainder into the next table step. See the following
 * example :-
 *
 * A read signal strength operation returns a value of 100
 * Subtract cal byte 1 (64) leaves 36 level > -113dBm
 * Subtract cal byte 2 (10) leaves 26 level > -103dBm
 * Subtract cal byte 3 (10) leaves 16 level > -93dBm
 * Subtract cal byte 4 (12) leaves 4 level > -83dBm
 * Test cal byte 5 (12) - no subtraction
 * Fine adjustment value = (remainder) / (cal byte 5) * (level step)
 *                       = 4 / 12 * 10 = 3dB
 * Signal level = -83dBm + 3dB = -80dB
 *
 * The receiver can operate the RF attenuator automatically if the signal
 * level is likely to overload the RF stages. Reading the RFAGC byte (page 0,
 * location 49) gives the attenuation in 10dB steps. This value should be
 * read and added to the value calculated above.
 */
int getCalLevel( RIG * rig, unsigned char rawAgc, int *dbm )
{
  int rc = -RIG_OK;
  int i;
  int raw = (int) rawAgc;
  int step = 10;
  unsigned char v;

  assert( NULL != rig );
  assert( NULL != dbm );

  for ( i = 0; i < rig->state.str_cal.size; i++ )
  {
    if ( 0 > ( raw - rig->state.str_cal.table[ i ].raw ) )
    {
      /* calculate step size */
      if ( 0 < i )
      {
        step = rig->state.str_cal.table[ i ].val -
	       rig->state.str_cal.table[ i - 1 ].val;
      }

      /* interpolate final value */
      *dbm += (int) ( ( (double) raw / (double) rig->state.str_cal.table[ i ].raw ) * (double) step );

      break;
    }
    else
    {
      raw = raw - rig->state.str_cal.table[ i ].raw;
      *dbm = rig->state.str_cal.table[ i ].val;
    }
  }

  /* Factor in RFAGC setting */
  rc = readByte( rig, WORKING, RFGAIN, &v );
  if ( RIG_OK == rc )
  {
    *dbm += ( (int) v * -10 ) + 10;
  }

  /* Adjust to S9 == 0 scale */
  *dbm += 73; /* S9 == -73 dBm */

  return ( rc );
}

/*
 * \brief Get bandwidth of given filter
 *
 * \param rig Pointer to rig struct
 * \param filter Filter number (1-6)
 *
 * \return Filter bandwidth in Hz, -1 on failure
 */
int getFilterBW( RIG *rig, enum FILTER_e filter )
{
  int rc = -1;
  unsigned char bw;

  rc = readByte( rig, BBRAM, (FL_BW + ((filter - 1) * 4)), &bw );
  if ( RIG_OK == rc )
  {
    rc = bcd2Int( bw ) * 100;
  }
  else
  {
    rc = -1;
  }

  rig_debug( RIG_DEBUG_VERBOSE, "%s: filter %1d BW %5d\n", __func__, filter, rc );

  return( rc );
}

/*
 * /brief Convert DDS steps to frequency in Hz
 *
 * /param steps DDS count
 *
 * /return Frequency in Hz or 0 on failure
 */
freq_t ddsToHz( const unsigned int steps )
{
  freq_t rc = 0.0;

  rc = ( (freq_t) steps * 44545000.0 / 16777216.0 );

  return( rc );
}

/*
 * /brief Convert frequency in Hz to DDS steps
 *
 * /param freq Frequency in Hz
 *
 * /return DDS steps (24 bits) or 0 on failure
 */
unsigned int hzToDDS( const freq_t freq )
{
  unsigned int rc = 0;
  double err[3] = { 0.0, 0.0, 0.0 };

  rc = (unsigned int) ( freq * 16777216.0 / 44545000.0 );

  /* calculate best DDS count based on bletcherous,
     irrational tuning step of 2.65508890151977539062 Hz/step
     (actual ratio is 44545000.0 / 16777216.0) */
  err[ 0 ] = fabs( freq - ddsToHz( (rc - 1) ) );
  err[ 1 ] = fabs( freq - ddsToHz( rc ) );
  err[ 2 ] = fabs( freq - ddsToHz( (rc + 1) ) );

  if ( err[ 0 ] < err[ 1 ] && err[ 0 ] < err[ 2 ] )
  {
    rc--;
  }
  else if ( err[ 2 ] < err[ 1 ] && err[ 2 ] < err[ 0 ] )
  {
    rc++;
  }

  rig_debug( RIG_DEBUG_VERBOSE, "%s: err[0 - 2] = %f %f %f rc 0x%08x\n",
	     __func__, err[ 0 ], err[ 1 ], err[ 2 ], rc );

  return( rc );
}

/*
 * /brief Convert PBS/BFO steps to frequency in Hz
 *
 * /param steps PBS/BFO offset steps
 *
 * /return Frequency in Hz or 0 on failure
 *
 * Max +ve offset is 127, max -ve offset is 128
 * Min -ve offset is 255
 */
float pbsToHz( const unsigned char steps )
{
  freq_t rc = 0.0;

  /* treat steps as a 1's complement signed 8-bit number */
  if ( 128 > steps )
  {
    rc = ( ( (float) steps * 12.5 * 44545000.0 ) / 16777216.0 );
  }
  else
  {
    rc = ( ( (float) (~steps & 0x7f) * -12.5 * 44545000.0 ) / 16777216.0 );
  }

  rig_debug( RIG_DEBUG_VERBOSE, "%s: raw %d hz %f\n", __func__, steps, rc );

  return( rc );
}

/*
 * /brief Convert PBS/BFO offset frequency in Hz to steps
 *
 * /param freq Offset frequency in Hz
 *
 * /return steps (8 bits) or 0 on failure
 */
unsigned char hzToPBS( const float freq )
{
  unsigned char rc;
  int steps;

  if ( 0 < freq )
  {
    steps = ( ( (freq + 0.5) * 16777216.0 ) / ( 44545000.0 * 12.5 ) );
  }
  else
  {
    steps = ( ( (freq - 0.5) * 16777216.0 ) / ( 44545000.0 * 12.5 ) );
  }

  rig_debug( RIG_DEBUG_VERBOSE, "%s: steps %d\n", __func__, steps );

  if ( 0 <= steps )
  {
    rc = (unsigned char) (steps & 0x7f);
  }
  else if ( -128 < steps )
  {
    rc = (unsigned char) (steps + 255);
  }
  else
  {
    rc = (unsigned char) 0;
  }

  rig_debug( RIG_DEBUG_VERBOSE, "%s: hz %f rc %d\n", __func__, freq, rc );

  return( rc );
}

/*
 * /brief Convert native Mode to Hamlib mode
 *
 * /param mode Native mode value
 *
 * /return Hamlib mode value
 */
rmode_t modeToHamlib( const unsigned char mode )
{
  rmode_t rc = RIG_MODE_NONE;

  switch( mode )
  {
  case AM:
    rc = RIG_MODE_AM;
    break;

  case SAM:
    rc = RIG_MODE_AMS;
    break;

  case FM:
    rc = RIG_MODE_FM;
    break;

  case DATA:
    rc = RIG_MODE_RTTY;
    break;

  case CW:
    rc = RIG_MODE_CW;
    break;

  case LSB:
    rc = RIG_MODE_LSB;
    break;

  case USB:
    rc = RIG_MODE_USB;
    break;

  default:
    break;
  };

  rig_debug( RIG_DEBUG_VERBOSE, "%s: Native %d, Hamlib %d\n",
             __func__, mode, rc );

  return( rc );
}

/*
 * /brief Convert Hamlib Mode to native mode
 *
 * /param mode Hamlib mode value
 *
 * /return Native mode value
 */
unsigned char modeToNative( const rmode_t mode )
{
  unsigned char rc = (unsigned char) MODE_NONE;

  switch( mode )
  {
  case RIG_MODE_AM:
    rc = (unsigned char) AM;
    break;

  case RIG_MODE_AMS:
    rc = (unsigned char) SAM;
    break;

  case RIG_MODE_FM:
    rc = (unsigned char) FM;
    break;

  case RIG_MODE_RTTY:
    rc = (unsigned char) DATA;
    break;

  case RIG_MODE_CW:
    rc = (unsigned char) CW;
    break;

  case RIG_MODE_LSB:
    rc = (unsigned char) LSB;
    break;

  case RIG_MODE_USB:
    rc = (unsigned char) USB;
    break;

  default:
    break;
  };

  rig_debug( RIG_DEBUG_VERBOSE, "%s: Hamlib %d, native %d\n",
             __func__, mode, rc );

  return( rc );
}

/*
 * /brief Convert native AGC speed to Hamlib AGC speed
 *
 * /param agc Native AGC speed value
 *
 * /return Hamlib AGC speed value
 */
enum agc_level_e agcToHamlib( const unsigned char agc )
{
  enum agc_level_e rc = RIG_AGC_AUTO;

  switch( agc )
  {
  case AGC_FAST:
    rc = RIG_AGC_FAST;
    break;

  case AGC_MED:
    rc = RIG_AGC_MEDIUM;
    break;

  case AGC_SLOW:
    rc = RIG_AGC_SLOW;
    break;

  case AGC_OFF:
    rc = RIG_AGC_OFF;
    break;

  default:
    break;
  };

  rig_debug( RIG_DEBUG_VERBOSE, "%s: Native %d, Hamlib %d\n",
             __func__, agc, rc );

  return( rc );
}

/*
 * /brief Convert Hamlib AGC speed to native AGC speed
 *
 * /param agc Hamlib AGC speed value
 *
 * /return Native AGC speed value
 */
unsigned char agcToNative( const enum agc_level_e agc )
{
  unsigned char rc = (unsigned char) AGC_NONE;

  switch( agc )
  {
  case RIG_AGC_OFF:
    rc = (unsigned char) AGC_OFF;
    break;

  case RIG_AGC_FAST:
    rc = (unsigned char) AGC_FAST;
    break;

  case RIG_AGC_SLOW:
    rc = (unsigned char) AGC_SLOW;
    break;

  case RIG_AGC_MEDIUM:
    rc = (unsigned char) AGC_MED;
    break;

  case RIG_AGC_SUPERFAST:
  case RIG_AGC_USER:
  case RIG_AGC_AUTO:
  default:
    rc = (unsigned char) AGC_NONE;
    break;
  };

  rig_debug( RIG_DEBUG_VERBOSE, "%s: Hamlib %d, native %d\n",
             __func__, agc, rc );

  return( rc );
}

/*
 * /brief Get page size
 *
 * /param page Page to get size of
 *
 * /return Page size, -1 on error
 */
int pageSize( const enum PAGE_e page )
{
  int rc = -1;

  if ( ( WORKING <= page ) && ( EEPROM3 >= page ) )
  {
    rc = (int) PAGE_SIZE[ page ];
  }
  else if ( ROM == page )
  {
    rc = (int) PAGE_SIZE[ page ];
  }
  else
  {
    rc = -1;
  }

  return( rc );
}

/*
 * /brief Set and execute IR controller code
 *
 * /param code IR code to execute
 *
 * \return RIG_OK on success, error code on failure
 */
int sendIRCode( RIG *rig, enum IR_CODE_e code )
{
  int rc = -RIG_EIO;
  unsigned char v = (unsigned char) code;

  assert( NULL != rig );

  rc = writeByte( rig, WORKING, IRCODE, v );
  if ( RIG_OK == rc )
  {
    rc = execRoutine( rig, SET_ALL );
    if ( RIG_OK == rc )
    {
      rig_debug( RIG_DEBUG_VERBOSE, "%s: set IR code %d\n", __func__, code );
    }
  }

  return( rc );
}

